import { ExecuteContext, K8SResourceType, KV, OperateFunction } from "yam-types"
import { HPABehavior, ScaleObjectSpec, ScaleTrigger } from "../types"

const KEDA_API_GROUP = "keda.sh"

const scaleOperator: OperateFunction<ScaleObjectSpec> = async (plan, diff, settings) => {
  if (!diff.hasDiff) {
    return
  }

  const scalerName = plan.data.workloadName + '-scaler'
  // clean deleted scaler
  if (diff.deletedItems.length > 0) {
    plan.log.info(`'scale' part has been deleted, will remove the ScaledObject: ${scalerName}`)
    plan.action(async (ctx) => {
      ctx.k8sClient.delete({
        kind: K8SResourceType.ScaledObject,
        name: scalerName
      })
    }, "delete-scaler:" + scalerName)
  }

  // sync configured scaler
  if (diff.currentItems.length > 0) {
    const scaleObj = diff.currentItems[0]
    plan.action(async (ctx) => {
      // a. check for KEDA operator and metrics server
      if (!ctx.k8sClient.crdExists(K8SResourceType.ScaledObject, KEDA_API_GROUP)) {
        ctx.log.error(`KEDA is not installed on cluster, action skipped. To install KEDA, please refer: https://keda.sh/docs/deploy`)
        return
      }
      if (scaleObj.triggers.length === 0) {
        ctx.log.warn(`no scaling trigger configured, action skipped.`)
        return
      }

      const type = (plan.data.currentModelFull?.deploy as KV)?.type
      if (type === K8SResourceType.Job || type == K8SResourceType.CronJob || type == K8SResourceType.DaemonSet) {
        ctx.log.warn(`scaler won't take effect on '${type}', action skipped.`)
        return
      }

      // b. append ScaledObject and TriggerAuthentication resources
      const workload = plan.data.workloadName
      const ns = plan.data.namespace
      const scaledObjectSpec = JSON.parse(JSON.stringify(scaleObj)) as ScaleObjectSpec
      scaledObjectSpec.scaleTargetRef = {
        kind: type,
        name: workload
      }
      manipulateTriggers(ctx, ns, workload, scaledObjectSpec.triggers, settings)

      // merge HPA behavior from settings
      if (settings.hpaBehavior) {
        mergeHpaBehavior(settings.hpaBehavior as HPABehavior, scaledObjectSpec)
      }

      ctx.mergeToYaml({
        name: scalerName,
        content: {
          kind: 'ScaledObject',
          metadata: {
            name: workload,
            labels: { workload }
          },
          spec: scaledObjectSpec
        }
      })
    }, "sync-scaler:" + scalerName)
  }
}

function getDefaultSetting(namespace: string, triggerType: string, defaultScaleSetting?: (DefaultTriggerAuthSetting | DefaultTriggerMetadataSetting)[]) {
  const defaultMixinSetting = defaultScaleSetting?.find(s => {
    if (s.forTriggerType !== triggerType) {
      return false
    }
    if (s.forNamespaces) {
      return s.forNamespaces.split(',').map(x => x.trim()).includes(namespace)
    }
    return true
  })
  return (defaultMixinSetting as DefaultTriggerAuthSetting)?.spec || (defaultMixinSetting as DefaultTriggerMetadataSetting)?.metadata || {}
}

function mergeHpaBehavior(hpaBehavior: HPABehavior, scaledObjectSpec: ScaleObjectSpec) {
  if (!scaledObjectSpec.advanced) {
    scaledObjectSpec.advanced = { horizontalPodAutoscalerConfig: { behavior: hpaBehavior } }
  } else if (!scaledObjectSpec.advanced?.horizontalPodAutoscalerConfig) {
    scaledObjectSpec.advanced.horizontalPodAutoscalerConfig = { behavior: hpaBehavior }
  } else if (!scaledObjectSpec.advanced.horizontalPodAutoscalerConfig.behavior) {
    scaledObjectSpec.advanced.horizontalPodAutoscalerConfig.behavior = hpaBehavior
  } else {
    Object.assign(scaledObjectSpec.advanced.horizontalPodAutoscalerConfig.behavior, hpaBehavior)
  }
}

function manipulateTriggers(ctx: ExecuteContext, ns: string, workload: string, triggers: ScaleTrigger[], settings: KV) {
  triggers.forEach(t => {
    t.metadata = Object.assign(getDefaultSetting(ns, t.type, settings.defaultMetadata as (DefaultTriggerMetadataSetting[] | undefined)), t.metadata)
    if (t.authentication) {
      // compose authenticationRef and TriggerAuthentication
      t.authenticationRef = {
        name: workload + '-' + t.authentication.name,
        kind: t.authentication.kind
      }
      if (t.authentication.spec) {
        ctx.mergeToYaml({
          name: `${workload}-${t.authentication.name}-trigger-auth`,
          content: {
            kind: t.authentication.kind || 'TriggerAuthentication',
            metadata: {
              name: workload + '-' + t.authentication.name
            },
            spec: Object.assign(getDefaultSetting(ns, t.type, settings.defaultAuthentication as (DefaultTriggerAuthSetting[] | undefined)), t.authentication.spec)
          }
        })
      }
      delete t.authentication
    }
  })
}

type DefaultTriggerAuthSetting = {
  forTriggerType: string,
  forNamespaces: string
  spec: unknown
}

type DefaultTriggerMetadataSetting = {
  forTriggerType: string,
  forNamespaces: string
  metadata: unknown
}

export default scaleOperator

